﻿#region Usings

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Web;
using System.Web.Caching;
using Rail.Net.Core.Interfaces.Data.Cacheable;

#endregion

namespace Rail.Net.Data.Caching
{
    public class WebCacheProvider : ICacheProvider
    {
        private readonly ReaderWriterLockSlim _cacheLock = new ReaderWriterLockSlim();

        private Cache _cache;

        public Cache Cache
        {
            get
            {
                if (_cache != null)
                {
                    return _cache;
                }
                _cache = HttpRuntime.Cache;
                return _cache;
            }
        }

        #region ICacheProvider Members

        public IEnumerable<T> GetOrCreateCache<T>(IQueryable<T> query)
        {
            _cacheLock.EnterUpgradeableReadLock();
            IEnumerable<T> result;
            try
            {
                var key = GetKey(query);
                var cacheItem = result = Cache.Get(key) as List<T>;
                if (cacheItem == null)
                {
                    _cacheLock.EnterWriteLock();
                    Cache.Insert(key, query.ToList());
                    result = query.AsEnumerable();
                    _cacheLock.ExitWriteLock();
                }
            }
            finally
            {
                _cacheLock.ExitUpgradeableReadLock();
            }
            return result;
        }

        public IEnumerable<T> GetOrCreateCache<T>(IQueryable<T> query, TimeSpan cacheDuration)
        {
            _cacheLock.EnterUpgradeableReadLock();
            IEnumerable<T> result;
            try
            {
                var key = GetKey(query);
                var cacheItem = result = Cache.Get(key) as List<T>;
                if (cacheItem == null)
                {
                    _cacheLock.EnterWriteLock();
                    Cache.Insert(key, query.ToList(), null, Cache.NoAbsoluteExpiration, cacheDuration);
                    result = query.AsEnumerable();
                    _cacheLock.ExitWriteLock();
                }
            }
            finally
            {
                _cacheLock.ExitUpgradeableReadLock();
            }
            return result;
        }

        public void RemoveFromCache<T>(IQueryable<T> query)
        {
            _cacheLock.EnterWriteLock();
            try
            {
                var key = GetKey(query);
                Cache.Remove(key);
            }
            finally
            {
                _cacheLock.ExitWriteLock();
            }
        }

        public void ClearCache<T>()
        {
            _cacheLock.EnterWriteLock();
            try
            {
                var keys = GetAllCacheKeys<T>();
                foreach (var key in keys)
                {
                    Cache.Remove(key);
                }
            }
            finally
            {
                _cacheLock.ExitWriteLock();
            }
        }

        #endregion

        private IEnumerable<string> GetAllCacheKeys<T>()
        {
            var result = new List<string>();
            var enumerator = Cache.GetEnumerator();
            while (enumerator.MoveNext())
            {
                if (enumerator.Key.ToString().StartsWith(typeof (T).FullName))
                {
                    result.Add(enumerator.Key.ToString());
                }
            }
            return result;
        }

        private static string GetKey<T>(IQueryable<T> query)
        {
            var key = string.Concat(typeof (T).FullName, "\n\r", query.ToString());
            return key;
        }
    }
}